package org.dromara.common.encrypt.filter;

import cn.hutool.core.util.ObjectUtil;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.dromara.common.core.constant.HttpStatus;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.encrypt.annotation.ApiEncrypt;
import org.dromara.common.encrypt.properties.ApiDecryptProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.io.IOException;

/**
 * Crypto 过滤器
 *
 * @author wdhcr
 */
public class CryptoFilter implements Filter {

    private static final Logger logger = LoggerFactory.getLogger(CryptoFilter.class);

    /**
     * API 解密属性配置
     */
    private final ApiDecryptProperties properties;

    /**
     * 构造函数，用于初始化 CryptoFilter 对象
     *
     * @param properties API 解密属性配置
     */
    public CryptoFilter(ApiDecryptProperties properties) {
        this.properties = properties;
    }

    /**
     * 业务处理，拦截要执行的请求，对请求和响应进行逻辑处理
     *
     * @param request  请求
     * @param response 响应
     * @param chain    过滤器链
     * @throws IOException      IO异常信息
     * @throws ServletException Servlet异常信息
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 将原始的 Servlet 请求和响应强制转换为 HTTP 类型，以便访问 HTTP 特定功能
        HttpServletRequest servletRequest = (HttpServletRequest) request;
        HttpServletResponse servletResponse = (HttpServletResponse) response;

        // 获取当前请求对应的 API 加密注解信息（如果有的话）
        ApiEncrypt apiEncrypt = this.getApiEncryptAnnotation(servletRequest);

        // 判断是否需要对响应内容进行加密处理：当存在加密注解且 response 属性为 true 时才开启响应加密
        boolean responseFlag = apiEncrypt != null && apiEncrypt.response();

        // 初始化包装器对象，用于后续的请求/响应处理
        ServletRequest requestWrapper = null;
        ServletResponse responseWrapper = null;
        EncryptResponseBodyWrapper responseBodyWrapper = null;

        // 检查当前请求是否为 PUT 或 POST 方法（这两种方法通常涉及数据提交）
        if (HttpMethod.PUT.matches(servletRequest.getMethod()) || HttpMethod.POST.matches(servletRequest.getMethod())) {
            // 从请求头中获取加密标识值，用于判断是否需要对请求体进行解密处理
            String headerValue = servletRequest.getHeader(properties.getHeaderFlag());

            // 如果请求头中包含了有效的加密标识，则执行请求体解密操作
            if (StringUtils.isNotBlank(headerValue)) {
                // 创建一个解密包装器，将原始请求包装成可以解密的形式
                requestWrapper = new DecryptRequestBodyWrapper(servletRequest, properties.getPrivateKey(), properties.getHeaderFlag());
            } else {
                // 如果没有加密标识但该接口标记了需要加密注解，则拒绝此请求访问权限
                if (ObjectUtil.isNotNull(apiEncrypt)) {
                    // 通过 Spring 上下文获取异常处理器，用来处理无权访问的情况
                    HandlerExceptionResolver exceptionResolver = SpringUtils.getBean("handlerExceptionResolver", HandlerExceptionResolver.class);
                    // 抛出服务异常，提示用户没有访问权限
                    exceptionResolver.resolveException(servletRequest, servletResponse, null, new ServiceException("没有访问权限，请联系管理员授权", HttpStatus.FORBIDDEN));
                    // 返回不再继续执行过滤器链
                    return;
                }
            }
        }

        // 如果检测到需要对响应内容进行加密处理
        if (responseFlag) {
            // 创建一个响应体包装器，用于捕获并修改响应内容
            responseBodyWrapper = new EncryptResponseBodyWrapper(servletResponse);
            // 设置响应包装器作为新的响应对象
            responseWrapper = responseBodyWrapper;
        }

        // 继续执行过滤器链中的下一个组件，传递可能已经被包装过的请求和响应对象
        chain.doFilter(ObjectUtil.defaultIfNull(requestWrapper, request), ObjectUtil.defaultIfNull(responseWrapper, response));

        // 如果之前设置了响应加密标志，则在此处对最终的响应内容进行加密处理
        if (responseFlag) {
            // 重置当前响应的状态和内容，准备写入新的加密后的内容
            servletResponse.reset();
            // 调用包装器的方法来获取已经加密后的响应内容，参数包括：原始响应对象、公钥（用于加密）、以及加密标识头名
            String encryptContent = responseBodyWrapper.getEncryptContent(servletResponse, properties.getPublicKey(), properties.getHeaderFlag());
            // 将加密后的字符串内容写回到客户端的响应流中
            servletResponse.getWriter().write(encryptContent);
        }
    }

    /**
     * 获取当前请求对应的 ApiEncrypt 加密注解信息
     *
     * @param servletRequest 请求
     * @return ApiEncrypt注解
     */
    private ApiEncrypt getApiEncryptAnnotation(HttpServletRequest servletRequest) {
        // 从Spring上下文中获取RequestMappingHandlerMapping Bean实例
        RequestMappingHandlerMapping handlerMapping = SpringUtils.getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class);
        // 获取注解
        try {
            // 根据请求获取对应的处理器映射链
            HandlerExecutionChain mappingHandler = handlerMapping.getHandler(servletRequest);
            // 检查是否成功获取到处理器映射链
            if (ObjectUtil.isNotNull(mappingHandler)) {
                // 获取实际的处理器对象（通常是Controller中的具体方法）
                Object handler = mappingHandler.getHandler();
                // 检查处理器是否存在且为HandlerMethod类型
                if (ObjectUtil.isNotNull(handler) && handler instanceof HandlerMethod handlerMethod) {
                    // 从方法上直接获取ApiEncrypt注解
                    return handlerMethod.getMethodAnnotation(ApiEncrypt.class);
                }
            }
        } catch (Exception e) {
            // 发生异常时返回null，避免影响正常流程
            logger.error("获取ApiEncrypt注解失败！{}", e.getMessage(), e);
            return null;
        }
        // 默认返回null
        return null;
    }

    /**
     * 销毁方法
     */
    @Override
    public void destroy() {
        logger.error("销毁方法执行了...");
    }
}
